#!/usr/bin/perl

#
# This is a tool to change the firmware bluetooth address. This tool will 
# not work on older version of firmware. This tool will look for 
# hcifw_bd_address[], once it finds it, it applies the bd address.
#
# Usage:
#      bdaddrpatch.pl firmware.hex mapfile.m51 xxXXyyYYzzZZ 
#
# firmware.hex is the intel hex file of the firmware
#    that need be changed.
# 
# xxXXyyYYzzZZ is the bluetooth address to be written into the firmware
#
#
#   The output file will be FW5001_xxXXyyYYzzZZ.hex in the current directory


@FirmwareImage;


$address = 0;
$length  = 0;

$C_INITSEG_START=0;
$C_INITSEG_END  =0;


$bd_start_addr = "";

$outFileName = "";

$inFileName = "";

if( $#ARGV != 2 )
{
    print "Usage: change_ba firmware.hex mapfile.m51 xxXXyyYYzzZZ > outputFile\n";

}
else
{
    $inFileName  = shift ARGV;
    ParseOneFile( \@FirmwareImage, $inFileName);

    ($C_INITSEG_START, $C_INITSEG_END, $bd_start_addr) = 
                                               ParseMapFile( shift ARGV );


    # extract the desired bd address
    $DesiredBDAddr = shift ARGV;


    if( $inFileName =~ /(.*?)(\.hex)/ )
    {
        $inFileName = $1;
    }

    @result = split( /\\/, $inFileName);

    $outFileBase = $result[$#result];

    $outFileName = $outFileBase. "_$DesiredBDAddr.hex";

#    print "out file name " , $outFileName,"\n";

    $DesiredBDAddr =~ /(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)/;

    $DesiredBDAddr = uc("$6$5$4$3$2$1");

    if( length($DesiredBDAddr) != 12)
    {
        print "BD address should be six bytes, xxXXyyYYzzZZ\n";
        exit 1;
    }

    open( outFileHndl, ">$outFileName") || 
                die "Can't open $outFileName for output\n";

    # look for the target address, 0xc8a0

    foreach $item (@FirmwareImage )
    {
        $address = hex $item->{"addr"};
        $length  = hex $item->{"len"};

        if(( $address > $C_INITSEG_START  ) &&
           ( $address  < $C_INITSEG_END) )
        {
#            print $item->{"addr"},"-->", $item->{"data"},"\n";

            # So we found something that is in C_INITSEG
            if( $item->{"data"} =~ /(\w*?)($bd_start_addr)(.{12})(.*)/)
            {
                # we find the starting address
                #
#                print $item->{"data"},"\n";

#                print "$1--$2--$3--$4\n";

                $item->{"data"} = "$1$2$DesiredBDAddr$4";

#                print "before calc. ", $item->{"data"},":\n";

                CalcChecksum( $item );
#                print $item->{"data"}, $item->{"checksum"};
                print "Change made\n";
            }
        }

        #generate intel hex file output

       print outFileHndl  ":",$item->{"len"},
                          $item->{"addr"},
                          $item->{"type"},
                          $item->{"data"},
                          $item->{"checksum"},
                          "\n";
    }


    

}

sub ParseMapFile()
{
    local($filename) = shift @_;

    open( file, $filename ) || die "Can't open $filename\n";

    while( $line = <file> )
    {
        # look for C_INITSEG
        if( $line =~ /C_INITSEG$/ )
        {
            $line =~ /^(\W*)CODE(\W*)([\d|A-F]*)H(\W*)([\d|A-F]*)/;
            $C_INITSEG_START = hex $3;
            $C_INITSEG_END  = $C_INITSEG_START + hex $5;
            if( $3 == 0 )
            {
                # this could be a multi-bank code
                $line =~ /^(\W*)BANK1(\W*)([\d|A-F]*)H(\W*)([\d|A-F]*)/;
                $C_INITSEG_START = hex $3;
                $C_INITSEG_END  = $C_INITSEG_START + hex $5;
            }

#            print "C_INITSEG_START = $C_INITSEG_START: $C_INITSEG_END\n";
        }

        # look for periph_bd_address
        if( $line =~ /periph_bd_address/ )
        {
            $line =~ /^(\W)*(X:)([\d|A-F]*)/;
            $bd_start_address = $3;
#            print "bd_start_address = :$bd_start_address:\n";
#            print "$line";
            return( $C_INITSEG_START, $C_INITSEG_END, $bd_start_address);
        }
    }
    return ();
}

sub ParseOneFile()
{
    local ( $image ) = shift @_;
    local($filename) = shift @_;
    local ($line);


    open( file, $filename ) || die "Can't open $filename\n";

    while( $line = <file> )
    {
        $record = {};

        $line =~ /:(\w\w)(\w{4})(\w\w)(.*)(\w\w)/;

        $record->{"len"} = $1;
        $record->{"addr"} = $2;
        $record->{"type"} = $3;
        $record->{"data"} = $4;
        $record->{"checksum"} = $5;

        push @$image, $record;
    }

    close(file);

}

sub CalcChecksum()
{
    local ($record) = @_;
    local ($tmp = 0 ); 
    local ($i = 0);
    local ($str = "");



    $tmp = hex $record->{"len"};


    $record->{"addr"} =~ /(\w\w)(\w\w)/;

    $tmp += hex $1;


    $tmp +=  hex $2;


    $tmp += hex $record->{"type"};


    # now let's work on the data part
    $str = $record->{"data"} ;
    for($i = 0; $i < length($str ) ; $i +=2 )
    {
        $tmp += hex( substr( $str , $i, 2 ) );

    }

    # take the modulo
    $tmp = $tmp % 256 ;

    $tmp = 0x100 - $tmp;

    # handle case when checksum is zero
    $tmp = $tmp % 256 ;

    $record->{"checksum"} = sprintf("%02X",$tmp);
}
